from django.shortcuts import render
from rest_framework.generics import ListAPIView
from rest_framework.views import APIView
from apps.oaauth.models import OADepartment
from apps.oaauth.serializers import DepartmentSerialize
from .serializers import StuffSerializer,ActiveStuffSerialize,StuffUploadSerialize
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.core.mail import send_mail
from django.conf import settings
from utils import aeser
from oaback_manage.celery import debug_task
from .tasks import send_email_task
from django.views import View
from django.http import JsonResponse
from apps.oaauth.models import ChoiceUserStatus
# python内置库
from urllib import parse
from rest_framework import generics,exceptions
from apps.oaauth.serializers import UserSerializer
from .paginations import StaffListPagination
from rest_framework import viewsets,mixins
from datetime import datetime
import json
import pandas as pd
from django.http.response import HttpResponse
from django.db import transaction

# 获取用户
OAUser = get_user_model()
# 创建aes对象
aes = aeser.AESCipher(settings.SECRET_KEY)
# Create your views here.

# Create your models here.
# 获取所有部门
class DepartmentListView(ListAPIView):
    queryset = OADepartment.objects.all()
    serializer_class = DepartmentSerialize

def send_active_email(request,email):
        token = aes.encrypt(email)
        # staff/active?token=xxx
        # 不能通过这种query的方式直接传入token 因为token经过加密后 可能会有“+”字符串 然后 点击该带有token的链接后 进入页面的url上是不能解析token字符串里面的+
        # url里面+有特殊意义
        # 所以这个时候需要提前 对+ 进行解析/编码
        # active_path = reverse("stuff:active_staff") + "?token=" + token
        active_path = reverse("stuff:active_staff") + "?" + parse.urlencode({"token": token})
        # http://127.0.0.1:8000/staff/active?token=xxxx
        # request.build_absolute_uri 将传入的字符串和 当前域名 进行拼接
        active_url = request.build_absolute_uri(active_path)
        # message = f'请点击以下链接激活账号：{active_url}'
        # send_mail(f'[tanzy_yl]账号激活', recipient_list=[email], message=msg,
        #           from_email=settings.DEFAULT_FROM_EMAIL)
        # 异步发送邮箱
        subject = f'RoseKiller后台账号激活'
        message = f'请点击以下链接激活账号：{active_url}'
        send_email_task.delay(email,subject,message)
        return Response()

class CeleryTest(APIView):
    def get(self,request):
        # 用celery异步执行debug_task这个任务
        # debug_task.delay()
        # return Response({"detail": "执行成功！"})
        token = request.GET.get('token')
        response = render(request,'active.html')
        response.set_cookie('token',token)
        return response

# 激活用户
# 因为这个地方不再使用前后端分离的形式 而是返回一个template 只有django自带的view即可
class ActiveStaffView(View):
    # 进入邮箱中发送的链接
    def get(self,request):
        # token = 'xJ4Fi9V93dn0xdk3mdkMiXzJOVH2jOt++Hve6HysKVZ+l/6q0AobfFpJQVapOOWi'
        # msg = aes.decrypt(token)
        # return Response(data={'email':msg})
        # 获取url上面的token参数
        token = request.GET.get('token')
        response = render(request, 'active.html')
        response.set_cookie('token', token)
        return response
    # 邮箱中链接的登录
    def post(self,request):
        # 注意这里的request是django中的request对象 不是rest_frame中的对象
        # 注意 这里的视图是继承django中的View  前端post方法传入的数据在request.POST中
        # 区别于rest_framework中的视图  前端传入的post、get等方法的数据都存才request.data中
        try:
            # 对在存储在浏览器里面的token进行解码
            token = request.COOKIES.get('token')
            email = aes.decrypt(token)
            serialize = ActiveStuffSerialize(data=request.POST)
            if serialize.is_valid():
                # 到数据库中进行比对token
                user = serialize.validated_data['user']
                form_email = serialize.validated_data['email']
                if email != form_email:
                    return JsonResponse({"code":400,"message": "邮箱错误"})
                user.status = ChoiceUserStatus.ACTIVE
                user.save()
                return JsonResponse({"code":200,"message": "激活成功！"})
            else:
                return JsonResponse({'code':400,'message':list(  serialize.errors.values())[0][0]})
        except Exception as e:
            print(e)
            return JsonResponse({"code":400,"message": "token错误!"})
# generics.UpdateAPIView
# 人员管理
# class StuffView(generics.ListCreateAPIView):
#     queryset = OAUser.objects.all()
#     # 指定分页
#     pagination_class = StaffListPagination
#     # serializer_class =
#     # 这里不能直接指定serializer_class 因为员工列表和新增员工不是使用的一个序列化
#     def get_serializer_class(self):
#         if self.request.method == 'GET':
#             return UserSerializer
#         else:
#             return StuffSerializer
#     # 获取员工列表
#     # 由于这里的员工列表需要分页 APIView中如果想要实现分页  需要多写一部分逻辑 这里我们改用generics.ListCreateAPIView  内置了分页逻辑(内置继承了ListModelMixin)
#     # ListCreateAPIView 默认只能支持post请求 和get请求
#     # 这里如果重写list方法 还是需要自己写分页逻辑
#     # 分页逻辑 默认使用了get_queryset  最终还是会返回到list方法中 给你实现分页
#     def get_queryset(self):
#         queryset = self.queryset
#         # 1.获取当前用户是否是董事会的 如果是 则返回所有员工
#         # 2.获取当前用户是否是部门leader  如果是 则返回部门所有员工
#         # 3.如果都不是 则无权访问
#         user = self.request.user
#         if user.department.name != '董事会':
#             if user.uid != user.department.leader.uid:
#                 raise exceptions.PermissionDenied()
#             else:
#                 queryset = queryset.filter(department_id=user.department_id)
#         return queryset.all()
#     # 新增员工
#     def create(self,request):
#         # 如果这里的视图采用的是视图集的话 这里的request则不需要传入  视图集中已经帮我们传入
#         serialize = StuffSerializer(data=request.data,context={'request':request})
#         if serialize.is_valid():
#             realname = serialize.validated_data.get('realname')
#             password = serialize.validated_data.get('password')
#             email = serialize.validated_data.get('email')
#             # 保存用户数据
#             user = OAUser.objects.create_user(email=email,realname=realname,password=password)
#             department = request.user.department
#             user.department = department
#             user.save()
#             # 对密码进行加密
#             # 我们不这样做 因为这样相当于执行了2句sql
#             # 因为执行OAUser.objects.create() 我们在OAUser中指定了objects = OAManager()
#             # 而我们重写的OAManager() 里面的_create方法会在我们执行create的时候执行一次，它内部里面设置了password 并且加密了 然后执行save保存到数据库中
#             # 如果这里我们还执行一次save已经设置password  相当于执行了二次sql语句
#             # user.set_password(password)
#             # user.save()
#             # 发送邮件
#             # 发送一个邮件 让用户点击这个链接后 跳转到激活页面 才能激活
#             # 为了区分用户 在发送链接邮件中 该链接中应该要包含这个用户的邮箱
#             # 针对邮箱需要加密  AES加密算法
#             # 发送激活邮件
#             # I/O操作：网络请求 文件读写 比较耗时的操作 一般都是放到异步去操作
#             # 对于发送邮件 因为是发送网络请求 可能发送出去的很慢
#             self.send_active_email(email)
#             return Response()
#         else:
#             return Response(data={'detail': list(serialize.errors.values())[0][0]},status=status.HTTP_400_BAD_REQUEST)
#
#     def send_active_email(self,email):
#         token = aes.encrypt(email)
#         # staff/active?token=xxx
#         # 不能通过这种query的方式直接传入token 因为token经过加密后 可能会有“+”字符串 然后 点击该带有token的链接后 进入页面的url上是不能解析token字符串里面的+
#         # url里面+有特殊意义
#         # 所以这个时候需要提前 对+ 进行解析/编码
#         # active_path = reverse("stuff:active_staff") + "?token=" + token
#         active_path = reverse("stuff:active_staff") + "?" + parse.urlencode({"token": token})
#         # http://127.0.0.1:8000/staff/active?token=xxxx
#         # request.build_absolute_uri 将传入的字符串和 当前域名 进行拼接
#         active_url = self.request.build_absolute_uri(active_path)
#         # message = f'请点击以下链接激活账号：{active_url}'
#         # send_mail(f'[tanzy_yl]账号激活', recipient_list=[email], message=msg,
#         #           from_email=settings.DEFAULT_FROM_EMAIL)
#         # 异步发送邮箱
#         subject = f'RoseKiller后台账号激活'
#         message = f'请点击以下链接激活账号：{active_url}'
#         send_email_task.delay(email,subject,message)
#         return Response()

# 人员管理
class StuffViewSet(
    viewsets.GenericViewSet,
    mixins.CreateModelMixin,
    mixins.UpdateModelMixin,
    mixins.ListModelMixin
):
    queryset = OAUser.objects.all()
    # 指定分页
    pagination_class = StaffListPagination
    # serializer_class =
    # 这里不能直接指定serializer_class 因为员工列表和新增员工不是使用的一个序列化
    def get_serializer_class(self):
        if self.request.method in ['GET','PUT']:
            return UserSerializer
        else:
            return StuffSerializer
    # 获取员工列表
    # 由于这里的员工列表需要分页 APIView中如果想要实现分页  需要多写一部分逻辑 这里我们改用generics.ListCreateAPIView  内置了分页逻辑(内置继承了ListModelMixin)
    # ListCreateAPIView 默认只能支持post请求 和get请求
    # 这里如果重写list方法 还是需要自己写分页逻辑
    # 分页逻辑 默认使用了get_queryset  最终还是会返回到list方法中 给你实现分页
    def get_queryset(self):
        query_params = self.request.query_params

        department_id = query_params.get('department_id')
        realname = query_params.get('realname')
        date_joined = query_params.get('date_joined')

        queryset = self.queryset
        # 1.获取当前用户是否是董事会的 如果是 则返回所有员工
        # 2.获取当前用户是否是部门leader  如果是 则返回部门所有员工
        # 3.如果都不是 则无权访问
        user = self.request.user
        if user.department.name != '董事会':
            if user.uid != user.department.leader.uid:
                raise exceptions.PermissionDenied()
            else:
                queryset = queryset.filter(department_id=user.department_id)
        else:
           try:
               if realname:
                   queryset = queryset.filter(realname__icontains = realname)
               if department_id:
                   queryset = queryset.filter(department_id=department_id)
               if date_joined:
                   date_start = datetime.strptime(date_joined[0],'%Y-%m-%d')
                   date_end = datetime.strptime(date_joined[1],'%Y-%m-%d')
                   queryset = queryset.filter(date_joined__range=(date_start,date_end))
           except Exception:
               pass
        return queryset.order_by('-date_joined')


    # 修改员工状态
    def update(self, request, *args, **kwargs):
        # 修改员工一个状态 只更新这个数据的一个字段
        # 原本的update会更新所有的字段 也就是需要传入model中定义的所有字段 并且全部更新
        kwargs['partial'] = True
        return super().update(request,*args,**kwargs)

    # 新增员工
    def create(self,request):
        # 如果这里的视图采用的是视图集的话 这里的request则不需要传入  视图集中已经帮我们传入
        serialize = StuffSerializer(data=request.data,context={'request':request})
        if serialize.is_valid():
            realname = serialize.validated_data.get('realname')
            password = serialize.validated_data.get('password')
            email = serialize.validated_data.get('email')
            # 保存用户数据
            user = OAUser.objects.create_user(email=email,realname=realname,password=password)
            department = request.user.department
            user.department = department
            user.save()
            # 对密码进行加密
            # 我们不这样做 因为这样相当于执行了2句sql
            # 因为执行OAUser.objects.create() 我们在OAUser中指定了objects = OAManager()
            # 而我们重写的OAManager() 里面的_create方法会在我们执行create的时候执行一次，它内部里面设置了password 并且加密了 然后执行save保存到数据库中
            # 如果这里我们还执行一次save已经设置password  相当于执行了二次sql语句
            # user.set_password(password)
            # user.save()
            # 发送邮件
            # 发送一个邮件 让用户点击这个链接后 跳转到激活页面 才能激活
            # 为了区分用户 在发送链接邮件中 该链接中应该要包含这个用户的邮箱
            # 针对邮箱需要加密  AES加密算法
            # 发送激活邮件
            # I/O操作：网络请求 文件读写 比较耗时的操作 一般都是放到异步去操作
            # 对于发送邮件 因为是发送网络请求 可能发送出去的很慢
            send_active_email(request,email)
            return Response()
        else:
            return Response(data={'detail': list(serialize.errors.values())[0][0]},status=status.HTTP_400_BAD_REQUEST)



class StaffDownloadView(APIView):
    def get(self,request):
        # /stuff/download?pk=['x','x']
        pks = request.query_params.get('pks')
        try:
            # 将pk转换成列表
            pks = json.loads(pks)
        except Exception:
            return Response({"detail": "传入参数格式错误！"},status=status.HTTP_400_BAD_REQUEST)
        try:
            curr_user = request.user
            queryset = OAUser.objects
            if curr_user.department.name != '董事会':
                # 不是董事会
                # 判断是否是部门leader
                if curr_user.department.leader_id != curr_user.uid:
                    return Response({"detail": "没有权限下载!"}, status=status.HTTP_403_FORBIDDEN)
                else:
                    # 如果用户是部门leader 就先将过滤出 当前用户部门的所有列表
                    queryset = queryset.filter(department_id=curr_user.department.id)
            queryset = queryset.filter(pk__in=pks)
            # 默认情况下queryset里面包含所有查询出来的表中所有字段
            # 但是我们不希望要所有字段 我们只希望要一部分字段
            # 告诉数据库 我们只需要查找指定字段
            # 这里department和User表是一个外键  如果相通过外键找到外键中表的字段需要加上__
            result = queryset.values("realname", "email", "department__name", "date_joined", "status")
            # 利用pandas生成excel文件
            # 创建DataFrame对象
            # result是一个queryset
            staff_df = pd.DataFrame(list(result))
            staff_df = staff_df.rename(
                columns={"realname": "姓名", "email": "邮箱", "department__name": "部门", "date_joined": "入职日期",
                         "status": "状态"})
            # 因为需要写入二进制文件数据 我们需要使用django.http.response中的HttpResponse
            response = HttpResponse(content_type='application/xlsx')
            # 表示我这个文件是以这个附件的形式进行下载 还指定了文件名
            response['Content-Disposition'] = 'attachment; filename=员工信息.xlsx'
            # 把员工的staff_df写入到Response中
            # pd.ExcelWriter指定把DataFrame写入到那个路径下面去
            # 或者缓存  这里我们写入到浏览器 所以传入response对象得到writer对象
            with pd.ExcelWriter(response) as writer:
                # sheet_name 为excel中左下角显示的名称
                # DateFrame对象通过to_excel写入到writer中
                # 底层又会将writer写入到response当中
                # to_excel可以指定一个路径 也可以知道一个writer
                staff_df.to_excel(writer, sheet_name="员工信息")
            return response
        except Exception as e:
            print(e)
            return Response({"detail": str(e)},status=status.HTTP_400_BAD_REQUEST)

class StaffUploadView(APIView):
    def post(self,request):
        serialize = StuffUploadSerialize(data=request.data)
        if serialize.is_valid():
            file = serialize.validated_data['file']
            user = request.user
            if user.department.name != '董事会' or user.department.leader_id!=user.uid:
                return Response({"detail": "您无权上传员工信息文件"},status=status.HTTP_400_BAD_REQUEST)

            # 读取excel文件
            staff_df = pd.read_excel(file)
            users = []
            # iterrows迭代器 每次循环只返回一行
            for index,row in staff_df.iterrows():
                # 获取部门
                if user.department.name != '董事会':
                    # 不是董事会的只能 发布当前部门的人员信息
                    department = user.department

                else:
                    try:
                        department = OADepartment.objects.filter(name=row['部门']).first()
                        # 不存在该部门
                        if not department:
                            return Response(f"该{department}不存在", status=status.HTTP_400_BAD_REQUEST)
                    except Exception:
                        return Response({"detail": "部门不存在"}, status=status.HTTP_400_BAD_REQUEST)

                try:
                    realname = row['姓名']
                except Exception:
                    return Response({"detail": "不存在realname列"},status=status.HTTP_400_BAD_REQUEST)
                try:
                    email = row['邮箱']
                except Exception:
                    return Response({"detail": "不存在email列"}, status=status.HTTP_400_BAD_REQUEST)
                password = '111111'
                # 这个地方如果使用create_user的话 它内部有save方法
                # 也就是每次循环后再创建用户的同时也会save 这样性能不高
                # 我们可以使用批量进行创建用户
                user = OAUser(email=email,realname=realname,department=department)
                user.set_password(password)
                users.append(user)
            # OAUser.objects.bulk_create(users)
            # 原子操作(事务)
            # 放到里面的内容 会当做事务处理 要么全部完成要么全部不完成
            # 因为这里的邮箱可能会重复 也就是会可能添加失败
            try:
                with transaction.atomic():
                    OAUser.objects.bulk_create(users)
            except Exception:
                return Response({"detail": "员工数据添加错误!"},status=status.HTTP_400_BAD_REQUEST)
            # 异步给每个员工发送邮件
            for user in users:
                send_active_email(request, user.email)
            return Response()
        else:
            detail = list(serialize.errors.values())[0][0]
            return Response({"detail": detail},status=status.HTTP_400_BAD_REQUEST)